package com.github.dote.mio.ack;

import com.github.dote.mio.client.IOClient;
import com.github.dote.mio.protocal.Packet;
import lombok.extern.slf4j.Slf4j;

import javax.swing.text.html.StyleSheet;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class AckSendTask {

    private final Map<String, AckSendTaskEntry> ackId2AckSendTaskEntry =  new ConcurrentHashMap<>();
    private Timer timer = null;
    private IOClient client;
    private int maxReSendCount = 1;
    private int checkInterval = 0;
    private int ackTimeout = 3;

    public AckSendTask(IOClient client) {
        this.client = client;
        this.maxReSendCount = client.getConfig().getMaxReSendCount();
        this.ackTimeout = client.getConfig().getAckTimeout();
        checkInterval = this.ackTimeout * 100000;
    }

    private void doTaskOnece() {
        List<AckSendTaskEntry> lostPacketList = new ArrayList<>();
        try {

            Iterator<Map.Entry<String, AckSendTaskEntry>> entryIt = ackId2AckSendTaskEntry.entrySet().iterator();
            while (entryIt.hasNext()) {
                Map.Entry<String, AckSendTaskEntry> entry = entryIt.next();
                String key = entry.getKey();

                final Packet p = entry.getValue().getPacket();
                final long t = entry.getValue().getTimestamp();
                if (p != null && p.isAck()) {
                    if (p.getRetryCount() >= maxReSendCount) {
                        AckSendTaskEntry lostEntry = new AckSendTaskEntry();
                        lostEntry.setPacket(p.clone());
                        lostEntry.setTimestamp(t);
                        lostPacketList.add(lostEntry);
                        remove(p.getAckId());
                    } else {
                        long delta = System.currentTimeMillis() - t;
                        if (delta <= client.getRttHandler().getRtoMilliseconds()) {

                        } else {
                            client.send(p);
                            p.increaseRetryCount();
                            entry.getValue().setTimestamp(System.currentTimeMillis());
                            client.getRttHandler().timeout();
                        }
                    }
                } else {
                    remove(key);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lostPacketList.size() > 0) {
                lostPacketList.forEach((lostAckSendEntry) -> {
                    client.getEventManager().onLostPacket(client, lostAckSendEntry.getPacket());
                });
            }
        }
    }


    public AckSendTask startup(boolean immediately) {
        stop();

        timer = new Timer(true);
        timer.scheduleAtFixedRate(new TimerTask() {
                                      @Override
                                      public void run() {
                                          doTaskOnece();
                                      }
                                  }
                , immediately ? 1 : checkInterval
                , 1);


        return this;
    }

    public void stop() {
        if (timer != null) {
            try {
                timer.cancel();
            } finally {
                timer = null;
            }
        }
    }

    public boolean isRunning() {
        return timer != null;
    }

    public boolean exist(String ackId) {
        return ackId2AckSendTaskEntry.get(ackId) != null;
    }

    public void put(IOClient client, Packet p) {
        if (p == null) {
            return;
        }
        if (p.getAckId() == null) {
            return;
        }

        if (!p.isAck()) {
            return;
        }

        if (ackId2AckSendTaskEntry.get(p.getAckId()) != null) {

        }

        AckSendTaskEntry entry = new AckSendTaskEntry();
        entry.setPacket(p.clone());
        entry.setTimestamp(System.currentTimeMillis());
        ackId2AckSendTaskEntry.put(p.getAckId(), entry);
    }

    public void remove(final String ackId) {
        AckSendTaskEntry entry = null;
        if ((entry = ackId2AckSendTaskEntry.remove(ackId)) != null) {
            if (entry.getPacket().getRetryCount() == 0 && client.getRttHandler() != null) {
                client.getRttHandler().stop();
            }
        }
    }
}
